/*
		Copyright (C) 2008 StudioCloud International Inc.

	This file is part of StudioCloud Log Watcher.
	
	Licensed under the Apache License, Version 2.0 (the "License"); 
	you may not use this file except in compliance with the License. 
	You may obtain a copy of the License at 
	
	http://www.apache.org/licenses/LICENSE-2.0 
	
	Unless required by applicable law or agreed to in writing, 
	software distributed under the License is distributed on 
	an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
	either express or implied. See the License for the specific 
	language governing permissions and limitations under the License. 
*/
/**
 * Methods for comparison
 * 1) Modification Time - Does not work on Windows because modification time is not updated in real time.  It is updated when the GUI OS does something.
 * 2) File Size - Does not work on Windows because modification time is not updated in real time.  It is updated when the GUI OS does something.
 * 3) File Data - Does not work on large files because it is truncated depending on the file stream buffer
 * 4) ByteArray Comparison - Works but the internal ByteArray compare is very slow
 * 5) ByteArray Length - Works and currently used
 **/ 
package com.studiocloud.io.FileSystem
{
	import com.studiocloud.io.FileSystem.Events.*;
	
	import flash.events.EventDispatcher;
	import flash.events.FileListEvent;
	import flash.events.TimerEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	
	import mx.collections.ArrayCollection;
	import mx.utils.ObjectUtil;
	
	public class FileSystemWatcher extends EventDispatcher
	{
		/**
		 * Needed Variables
		 **/
		private var watchedFilePath:String = "";
		private var watchedFile:File;
		private var watchedFileLength:Number = 0;
		private var watchedFileIsDirectory:Boolean = false;
		
		private var timer:Timer;
		private var timerRefresh:Number = 5000;
		
		/**
		 * Events that may be dispatched by FileSystemWatcher
		 **/
		public static const DIRECTORY_FILE_ADDED:String = DirectoryFileAddedEvent.eventName;
		public static const DIRECTORY_FILE_DELETED:String = DirectoryFileDeletedEvent.eventName;
		public static const DIRECTORY_FILE_CHANGED:String = DirectoryFileChangedEvent.eventName;
		public static const FILE_CHANGED:String = FileChangedEvent.eventName;
		public static const FILE_DELETED:String = FileDeletedEvent.eventName;
		public static const FILE_SYSTEM_WATCHER_ERROR:String = FileSystemWatcherErrorEvent.eventName;
		public static const FILE_SYSTEM_WATCHER_START:String = FileSystemWatcherStartEvent.eventName;
		public static const FILE_SYSTEM_WATCHER_STOP:String = FileSystemWatcherStopEvent.eventName; 
		
		/**
		 * Directory Properties
		 **/		
		 
		/**
		 * This contains a list of File objects that contiains all the needed comparative information
		 **/
		private var watchedFileList:Array;
		private var watchedFileSizes:ArrayCollection;
		
		/**
		 * File Properties
		 **/
		 
		/**
		 * Properties for a specific file are not needed because they are already contained in the watchedFile variable
		 **/
		
		/**
		 * Constructor
		 **/
		public function FileSystemWatcher(path:String)
		{
			watchedFilePath = path;
			watchedFile = new File(watchedFilePath);
			watchedFileIsDirectory = watchedFile.isDirectory;
		}
		
		/**
		 * Methods
		 **/
		public function beginWatching(delay:Number = 5000):void {
			if (!watchedFile.exists) {
				this.dispatchEvent(new FileSystemWatcherErrorEvent(FileSystemWatcherErrorEvent.eventName, watchedFile.url + " does not exist"));
			}
			else if (delay < 5000) {
				this.dispatchEvent(new FileSystemWatcherErrorEvent(FileSystemWatcherErrorEvent.eventName, "Delay must be greater than or equal to 5000 milliseconds."));
			}
			else {
				timerRefresh = delay;
				checkFile(true);
			}
		}
		
		public function endWatching():void {
			if (timer != null) {
				timer.stop();
				timer = null;
				timerRefresh = 5000;
			}
			
			watchedFilePath = "";
			watchedFile = null;
			watchedFileLength = 0;
			watchedFileIsDirectory = false;
			watchedFileList = new Array();
		}
		
		public function scheduleNextQuery():void {
            if (this.timer != null)
            {
            	this.timer.stop();
            }
                
            this.timer = new Timer(timerRefresh);
            if (!this.timer.hasEventListener(TimerEvent.TIMER))
            {
            	this.timer.addEventListener(TimerEvent.TIMER, onNextQuery);
            }
            this.timer.start();
		}
		
		public function onNextQuery(event:TimerEvent):void {
			checkFile(false);
		}
		
		public function getFileBeingWatched():String {
			return watchedFilePath;
		}
		
		public function isFileBeingWatched():Boolean {
			if (timer != null) {
				return true;
			}
			else {
				return false;
			}
		}
		
		private function checkFile(isInitial:Boolean = false):void {
			if (watchedFileIsDirectory) {
				if (isInitial) {
					watchedFile.addEventListener(FileListEvent.DIRECTORY_LISTING, initialDirectoryListingHandler);
					watchedFile.getDirectoryListingAsync();
				}
				else {
					getDirectoryDetails();
				}
			}
			else {
				if (isInitial) {
					var tempFileBytes:ByteArray = getFileContentsAsByteArray(watchedFile);
					watchedFileLength = tempFileBytes.length;
				}
				getFileDetails();
			}
		}
		
		/**
		 * Directory Functions
		 **/
		private function getDirectoryDetails():void {
			watchedFile.addEventListener(FileListEvent.DIRECTORY_LISTING, directoryListingHandler);
			watchedFile.getDirectoryListingAsync();
		}
		
		private function initialDirectoryListingHandler(event:FileListEvent):void {
			watchedFile.removeEventListener(FileListEvent.DIRECTORY_LISTING, initialDirectoryListingHandler);
			watchedFileList = event.files;
			watchedFileSizes = fileListToObjectList(watchedFileList);
			
		    compareDirectoryLists(watchedFileList);
		}
		
		private function directoryListingHandler(event:FileListEvent):void {
			watchedFile.removeEventListener(FileListEvent.DIRECTORY_LISTING, directoryListingHandler);
		    compareDirectoryLists(event.files);
		}
		
		private function compareDirectoryLists(currentList:Array):void {
			var sizesList:ArrayCollection = fileListToObjectList(currentList);
			
			for (var i:int = 0; i < watchedFileSizes.length; i++) {
				var file1:Object = watchedFileSizes.getItemAt(i);
				var foundFile:Object;
				for each (var file2:Object in sizesList) {
					if (ObjectUtil.compare(file1.url, file2.url) == 0) {
						foundFile = file2;
					}
				}
				
				if (foundFile != null) {
					if (file1.length != foundFile.length) {
						this.dispatchEvent(new FileChangedEvent(FileChangedEvent.eventName, watchedFile));
					
						this.dispatchEvent(new DirectoryFileChangedEvent(DirectoryFileChangedEvent.eventName, watchedFileList[i] as File));
					}
				}
				else {
					this.dispatchEvent(new FileChangedEvent(FileChangedEvent.eventName, watchedFile));
					
					this.dispatchEvent(new DirectoryFileDeletedEvent(DirectoryFileDeletedEvent.eventName, watchedFileList[i] as File));
				}
				
				foundFile = null;
			}
			
			//Check to see if any files have been added
			for (var i:int = 0; i < sizesList.length; i++) {
				var file2:Object = sizesList.getItemAt(i);
				var foundFile:Object;
				for each (var file1:Object in watchedFileSizes) {
					if (ObjectUtil.compare(file1.url, file2.url) == 0) {
						foundFile = file2;
					}
				}
				
				if (foundFile == null) {
					this.dispatchEvent(new FileChangedEvent(FileChangedEvent.eventName, watchedFile));
					
					this.dispatchEvent(new DirectoryFileAddedEvent(DirectoryFileAddedEvent.eventName, currentList[i] as File));
					
					watchedFileList = ObjectUtil.copy(currentList) as Array;
				}
				
				foundFile = null;
			}
			
			watchedFileList = new Array();
			for each (var temp:File in currentList) {
				watchedFileList.push(temp);
			}
			watchedFileSizes = fileListToObjectList(watchedFileList);
			
			scheduleNextQuery();
		}
		
		private function fileListToObjectList(tempArray:Array):ArrayCollection {
			var ac:ArrayCollection = new ArrayCollection();
			for each (var file1:File in tempArray) {
				var byteArray:ByteArray;
				if (!file1.isDirectory) {
					byteArray = getFileContentsAsByteArray(file1);
				}
				ac.addItem({url: file1.url, isDirectory: file1.isDirectory, length: byteArray.length});
			}
			return ac;
		}
		
		/**
		 * File Methods
		 **/
		private function getFileDetails():void {
			var tempFile:File = new File(watchedFilePath);
			if (tempFile.exists) {
				var tempFileBytes:ByteArray = getFileContentsAsByteArray(tempFile);
		
				if (watchedFileLength != tempFileBytes.length) {
					this.dispatchEvent(new FileChangedEvent(FileChangedEvent.eventName, watchedFile));
					watchedFileLength = tempFileBytes.length;
				}
				scheduleNextQuery();
			}
			else {
				this.dispatchEvent(new FileDeletedEvent(FileDeletedEvent.eventName, watchedFile));
				
				if (watchedFile != null) 
					this.dispatchEvent(new FileSystemWatcherErrorEvent(FileSystemWatcherErrorEvent.eventName, watchedFile.url + " does not exist"));
			}
		}
		
		private function getFileContentsAsByteArray(inputFile:File):ByteArray {
			var bytes:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(inputFile, FileMode.READ);
			stream.readBytes(bytes);
			stream.close();
			return bytes;
		}
		
		private function getFileContentsAsString(inputFile:File):String {
			var fileData:String = "";
			var stream:FileStream = new FileStream();
			stream.open(inputFile, FileMode.READ);
			fileData = stream.readUTF();
			stream.close();
			return fileData;
		}
	}
}